﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Reflection;

namespace Atlepage.Syntactic
{
    abstract class ProductionNode
    {
        private string name;

        public string Name { get { return name; } }

        protected virtual void Initialize(string s)
        {
            name = s;
        }

        public override int GetHashCode()
        {
            return name.GetHashCode();
        }

        public override bool Equals(object obj)
        {
            return base.Equals(obj) || ((ProductionNode)obj).name == name;
        }

        public override string ToString()
        {
            return name;
        }
    }

    class Modifier : ProductionNode
    {
        public Modifier(string s) { Initialize(s); }
    }

    class Terminal : ProductionNode
    {
        public int Type { get; private set; }
        public Terminal(string s, int type) { Initialize(s); Type = type; }
    }

    class NonTerminal : ProductionNode
    {
        List<Production> productions = new List<Production>();

        public NonTerminal(string s) { Initialize(s); }

        internal void AddProduction(Production p)
        {
            productions.Add(p);
        }

        public bool HasProductions
        {
            get { return productions.Count > 0; }
        }

        public IEnumerable<Production> Productions
        {
            get
            {
                foreach (var item in productions)
                {
                    yield return item;
                }
            }
        }
    }

    class Production
    {
        MethodInfo method;
        NonTerminal name;
        ProductionNode[] nodes;
        string cachedString;

        internal NonTerminal Name { get { return name; } }
        internal ProductionNode[] Nodes { get { return nodes; } }
        internal MethodInfo Method { get { return method; } }

        internal Production(MethodInfo method, NonTerminal name, IEnumerable<ProductionNode> nodes)
        {
            this.method = method;
            this.name = name;
            this.nodes = nodes.ToArray();
            this.name.AddProduction(this);
        }

        private string CachedString
        {
            get
            {
                if (cachedString == null)
                {
                    StringBuilder sb = new StringBuilder();
                    sb.Append(Name);
                    sb.Append(" :");
                    foreach (var item in Nodes)
                    {
                        sb.Append(" ");
                        sb.Append(item);
                    }
                    cachedString = sb.ToString();
                }
                return cachedString;
            }
        }

        public override int GetHashCode()
        {
            return CachedString.GetHashCode();
        }

        public override bool Equals(object obj)
        {
            return base.Equals(obj) || ((Production)obj).CachedString == CachedString;
        }

        public override string ToString()
        {
            return CachedString.ToString();
        }

        public static IEnumerable<Production> Parse(MethodInfo method, string name, string rule, IEnum kind, Dictionary<string, NonTerminal> nonTerms, Dictionary<string, Terminal> terms)
        {
            return from r in rule.Split('|')
                   select new Production(method, (NonTerminal)CreateNode(name, kind, nonTerms, terms),
                       from n in Regex.Split(r.Trim(), @"\s+")
                       select CreateNode(n, kind, nonTerms, terms));
        }

        private static ProductionNode CreateNode(string s, IEnum kind, Dictionary<string, NonTerminal> nonTerms, Dictionary<string, Terminal> terms)
        {
            int tokenkind;
            if (s[0] == '%')
                return new Modifier(s);
            else if (kind.TryGetValue(s, out tokenkind))
            {
                Terminal t;
                if (!terms.TryGetValue(s, out t))
                {
                    t = new Terminal(s, tokenkind);
                    terms.Add(s, t);
                }
                return t;
            }
            else
            {
                NonTerminal nt;
                if (!nonTerms.TryGetValue(s, out nt))
                {
                    nt = new NonTerminal(s);
                    nonTerms.Add(s, nt);
                }
                return nt;
            }
        }
    }
}
